// Dynamic text widget implmenentation for Gtk+
// Copyright 2006 (C) Ralph Thomas

#include <view/scroll.h>
#include <view/rectangle.h>
#include <adobe/eve.hpp>
#include <iostream>
#include <gtk/os_utilities.hpp>
#include <gtk/gtk.h>

namespace view {
	//
	/// This class contains a scrollview that contains a single
	/// GtkFixed layout manager. It has it's own private Eve
	/// instance to manage the layout.
	//
	class scrollview {
		typedef				adobe::implementation::signal_wrapper_zero_t<void> on_scroll_t;
		typedef				adobe::implementation::signal_wrapper_one_t<void, GtkWidget*> on_focus_t;
		adobe::auto_ptr<GtkWidget>	scrolled_window_m;
		adobe::auto_ptr<GtkWidget>	viewport_m;
		adobe::auto_ptr<GtkWidget>	layout_m;
		adobe::eve_t			eve_m;
		adobe::eve_t::iterator		root_view_m;
		adobe::display_t::position_t	display_token_m;
		adobe::display_t::position_t	overlay_token_m;
		//
		// This gets called whenever one of the children takes focus.
		// It's used to implement the gravity mode.
		//
		on_focus_t			on_focus_m;
		//
		// These need to appear after the widget definitions so that
		// they get destroyed first (ugh). Really the signal wrappers
		// should increment the refcount when they connect...
		//
		on_scroll_t			on_scroll_horiz_m;
		on_scroll_t			on_scroll_vert_m;
		on_scroll_t			on_change_horiz_m;
		on_scroll_t			on_change_vert_m;
		//
		// This is the bounding box rectangle that we have to maintain
		// for the bounding_box optimization.
		//
		rectangle			bounding_box_m;
		//
		// The name of the bounding box cell, and the sheet that
		// contains it.
		//
		adobe::name_t			bounding_box_cell_m;
		adobe::sheet_t&			sheet_m;
		//
		/// Whenever one of the scrollbars changes, this function
		/// gets called. A scrollbar change includes the size of
		/// the window changing, etc.
		///
		/// This function will trigger a relayout when required,
		/// and also populates the bounding_box cell.
		//
		void on_scroll() {
			rectangle new_box;
			GtkAdjustment* hadj(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())));
			GtkAdjustment* vadj(gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())));
			new_box.x = (long)gtk_adjustment_get_value(hadj);
			new_box.y = (long)gtk_adjustment_get_value(vadj);
			new_box.w = (long)hadj->page_size;
			new_box.h = (long)vadj->page_size;
			if( (bounding_box_m.w != new_box.w) || (bounding_box_m.h != new_box.h) ) {
				//
				// The sizes have changed. We need to perform an adjustment
				// to our Eve engine.
				//
				eve_m.evaluate_adjust( new_box.w, new_box.h, adobe::eve_t::evaluate_nested );
			}
			if( new_box != bounding_box_m ) {
				//
				// The box is different, push the new value into the sheet
				// if we can.
				//
				bounding_box_m = new_box;
				if( !bounding_box_cell_m ) return;
				sheet_m.set( bounding_box_cell_m, adobe::value_t( bounding_box_m ) );
				sheet_m.update();
			}
		}
		void on_focus(GtkWidget* child) {
			//
			// This function never gets called :(.
			// If it did then we could implement
			// gravity here.
			//
		}
	  public:
		scrollview( const adobe::dictionary_t& parameters, const adobe::widget_node_t& parent, const adobe::factory_token_t& token, factory* f )
		 : scrolled_window_m(0), layout_m(0), sheet_m(token.sheet_m) {
			//
			// Extract all of the parameters that we can
			// from the dictionary.
			//
			std::string style;
			GtkPolicyType hscroll(GTK_POLICY_AUTOMATIC);
			GtkPolicyType vscroll(GTK_POLICY_AUTOMATIC);
			adobe::name_t scrollbars( adobe::static_name_t( "both" ) );
			parameters.get( adobe::static_name_t( "scrollbars" ), scrollbars );
			if( scrollbars == adobe::static_name_t( "horizontal" ) ) vscroll = GTK_POLICY_NEVER;
			else if( scrollbars == adobe::static_name_t( "vertical" ) ) hscroll = GTK_POLICY_NEVER;
			parameters.get( adobe::static_name_t( "virtual_box" ), bounding_box_cell_m );
			parameters.get( adobe::static_name_t( "style" ), style );
			//
			// Create all of our widgets.
			//
			scrolled_window_m.reset( gtk_scrolled_window_new(0, 0) );
			viewport_m.reset( gtk_viewport_new( 
				gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())),
				gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get()))) );
			gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window_m.get()), hscroll, vscroll );
			layout_m.reset( gtk_fixed_new() );
			//
			// HACK: I want the background to be white,
			// but hardcoding it here won't fit in with
			// themes...
			//
	//		GdkColor white;
	//		white.pixel = 0; white.red = white.green = white.blue = 65535;
	//		gtk_widget_modify_bg( viewport_m.get(), GTK_STATE_NORMAL, &white );
			if( !style.empty() ) gtk_widget_set_name( viewport_m.get(), style.c_str() );
			//
			// Make sure that we get notified when the
			// scroll happens so that we can update our
			// bounding box.
			//
			on_scroll_horiz_m.set_callback( boost::bind( &scrollview::on_scroll, this ) );
			on_scroll_horiz_m.connect( gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())), "value-changed" );
			on_scroll_vert_m.set_callback( boost::bind( &scrollview::on_scroll, this ) );
			on_scroll_vert_m.connect( gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())), "value-changed" );
			on_change_horiz_m.set_callback( boost::bind( &scrollview::on_scroll, this ) );
			on_change_horiz_m.connect( gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())), "changed" );
			on_change_vert_m.set_callback( boost::bind( &scrollview::on_scroll, this ) );
			on_change_vert_m.connect( gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolled_window_m.get())), "changed" );
			//
			// Add our scroll window widget to the parent
			// layout.
			//
			GtkWidget* tmp(scrolled_window_m.get());
			display_token_m = adobe::insert( adobe::get_main_display(), parent.display_token_m, tmp );
			overlay_token_m = token.overlay_m.insert( parent.overlay_token_m );
			//
			// Insert the viewport into the scrolled window,
			// then insert the fixed layout into the viewport.
			// We take this long route to get a working
			// display token out.
			//
			tmp = viewport_m.get();
			display_token_m = adobe::insert( adobe::get_main_display(), display_token_m, tmp );
			tmp = layout_m.get();
			display_token_m = adobe::insert( adobe::get_main_display(), display_token_m, tmp );
			//
			// Make sure that we get told when one of
			// our children takes focus.
			//
			// XXX: Why-oh-why doesn't this work?!?
			//
			on_focus_m.set_callback( boost::bind( &scrollview::on_focus, this, _1 ) );
			on_focus_m.connect( layout_m.get(), "set-focus-child" );
			//
			// Create a factory token for our children. It
			// needs to use our own layout as the layout
			// manager (and we will return the GtkFixed
			// widget as ourselves for being a parent for
			// children).
			//
			eve_m.set_preserve_size( true );
			factory_token_ptr_t child_token;
			child_token.layout_m = &eve_m;
			f->push( factory::factory_t( child_token, 0 ) );
			//
			// Add the scrolled window to our parent's Eve
			// layout.
			//
			adobe::extents_t extents;
			int min_width( 160 ); int min_height( 160 );
			parameters.get<int>( adobe::static_name_t( "min_width" ), min_width );
			parameters.get<int>( adobe::static_name_t( "min_height" ), min_height );
			extents.width() = min_width; extents.height() = min_height;
			//
			// Build the signals for the scrolled window.
			//
			adobe::eve_t::signal_suite_t signals;
			signals.eve_calculate_proc_m = boost::bind( &eve_client::utility_widget_calculate, extents, _1 );
			eve_client::place_func_t placer = boost::bind( &adobe::implementation::set_bounds_widget, scrolled_window_m.get(), _1, _2 );
			signals.eve_place_proc_m = boost::bind( &eve_client::utility_widget_place, placer, _1, _2, boost::ref( token.overlay_m ), overlay_token_m );
			//
			// We really don't care about the eve token
			// returned here; we give the eve token of
			// the fixed container to children.
			//
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					adobe::eve_t::calculate_data_t(),
					false,
					parameters,
					signals
					)
				);
			//
			// We need to add our fixed layout to our own
			// Eve instance as the top-level container.
			// Then Eve will manage resizing it for us.
			//
			// This root container doesn't have any bounds
			// of itself (the child container can put border,
			// etc. in).
			// 
			adobe::eve_t::calculate_data_t cdata;
			//
			// Even though we do have a real widget (implying
			// that create_m == true), Eve can't do anything
			// with it.
			//
			cdata.create_m = false;
			cdata.placement_m = adobe::eve_t::place_row;
			root_view_m = eve_m.add_view_element( adobe::eve_t::iterator(),
				adobe::eve_t::insert_element_t(
					cdata,
					true, // we are a container
					adobe::dictionary_t(),
					adobe::eve_t::signal_suite_t()
					)
				);
		}
		~scrollview() {}
		//
		/// Return the widget_node structure for this widget. This
		/// looks a bit funny for the scrollview because it contains
		/// it's own layout engine.
		//
		adobe::widget_node_t node( size_enum_t size ) const {
			return adobe::widget_node_t( size, root_view_m, display_token_m, overlay_token_m );
		}
	};
	adobe::widget_node_t scroll_factory(
		const adobe::dictionary_t& 	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token,
		factory*			f )
	{
		scrollview* sv( new scrollview( parameters, parent, token, f ) );
		token.assemblage_m.delete_on_destruction( sv );
		return sv->node( parent.size_m );
	}
}
